/**
 * 
 */
package commons.base.http;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.codec.binary.Base64;

import commons.base.util.GzipUtil;
import commons.base.util.StreamUtil;

/**
 * JDK httpclient封装
 * @author yuan<cihang.yuan@happyelements.com>
 * @since 1.7
 */
public class SimpleHttpClient {
	
	/**
	 * 构造PSOT请求
	 * @param uri
	 * @return
	 */
	public static RequestBuilder newPost(String uri){
		return new RequestBuilder().setUri(uri).usePostMethod();
	}
	
	/**
	 * 构造GET请求
	 * @param uri
	 * @return
	 */
	public static RequestBuilder newGet(String uri){
		return new RequestBuilder().setUri(uri).useGetMethod();
	}
	
	private static void readResponseAsFile(HttpURLConnection conn, File file)
			throws IOException {
		InputStream is = conn.getErrorStream();
		if (is == null) {
			is = conn.getInputStream();
		}
		
		StreamUtil.readStreamAsFile(is, file, false);
	}
	
	private static byte[] readResponse(HttpURLConnection conn) throws IOException{
		InputStream is = conn.getErrorStream();
		if(is == null){
			is = conn.getInputStream();
		}
		
		return readInputStream(is);
	}
	private static byte[] readInputStream(InputStream is)throws IOException{
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		int data = 0;
		while((data = is.read()) != -1){
			baos.write(data);
		}
		
		return baos.toByteArray();
	}
	
	private static String buildParamter(Map<String, String> paramMap){
		StringBuilder sb = new StringBuilder();
		if(notEmpty(paramMap)){
			for(Map.Entry<String, String> entry : paramMap.entrySet()){
				sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
			}
		}
		sb = compareAndDeleteLastChar(sb, '&');
		return sb.toString();
	}
	
	private static boolean notEmpty(Map<?,?> map) {
		return (map != null) && (!map.isEmpty());
	}
	
	private static StringBuilder compareAndDeleteLastChar(StringBuilder sb, char c){
		if((sb.length() > 0) && (sb.charAt(sb.length() - 1) == c)){
			sb = sb.deleteCharAt(sb.length() - 1);
		}
		return sb;
	}
	
	public static class RequestBuilder {
		/** http请求方法 */
		private String method;
		private String uri;
		/** http请求头 */
		private Map<String, String> headerMap = new HashMap<String, String>();
		/** 表单参数 */
		private Map<String, String> form = new HashMap<String, String>();
		/** http body */
		private String bodyString;
		/** 连接超时 */
		private int connectTimeout = 30000;
		/** 读超时 */
		private int readTimeout = 30000;
		private String charset = "UTF-8";
		/** 代理 */
		private Proxy proxy;
		
		public RequestBuilder useGetMethod() {
			this.method = "GET";
			return this;
		}
		public RequestBuilder usePostMethod() {
			this.method = "POST";
			return this;
		}
		public RequestBuilder usePutMethod() {
			this.method = "PUT";
			return this;
		}
		public RequestBuilder useDeleteMethod() {
			this.method = "DELETE";
			return this;
		}
		public RequestBuilder setUri(String uri) {
			this.uri = uri;
			return this;
		}
		
		/**
		 * 添加http请求头
		 * @param name
		 * @param value
		 * @return
		 */
		public RequestBuilder addHeader(String name, String value){
			headerMap.put(name, value);
			return this;
		}
		
		/**
		 * 添加表单字段. 与setBodyString互斥
		 * @param name
		 * @param value
		 * @return
		 */
		public RequestBuilder addFormField(String name, String value){
			form.put(name, value);
			return this;
		}
		
		/**
		 * 设置http body. 与 addFormField 互斥
		 * @param bodyString
		 * @return
		 */
		public RequestBuilder setBodyString(String bodyString) {
			this.bodyString = bodyString;
			return this;
		}

		public RequestBuilder setConnectTimeout(int connectTimeout) {
			this.connectTimeout = connectTimeout;
			return this;
		}
		
		public RequestBuilder setReadTimeout(int readTimeout) {
			this.readTimeout = readTimeout;
			return this;
		}
		
		public RequestBuilder setCharset(String charset) {
			this.charset = charset;
			return this;
		}
		
		private boolean isPosttRequest(){
			return method.equals("POST") || method.equals("PUT");
		}
		
		public RequestBuilder setProxy(String proxyHost, int proxyPort, String username, String password){
			proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort));
			
			if(username != null){
				headerMap.put("Proxy-Authorization", "Basic " + Base64.encodeBase64String(new String(username+":"+password).getBytes()));
			}
			
			return this;
		}

		public HttpResponse execute() throws IOException{
			return execute(false);
		}
		
		/**
		 * 实际执行http请求并返回字符串结果
		 * @return
		 * @throws IOException
		 */
		public HttpResponse execute(boolean supportRequestBodyCompress) throws IOException{
			HttpResponse response = new HttpResponse();
			HttpURLConnection conn = null;
			
			try{
				if(!isPosttRequest()){
					// 构造http get url
					buildGetUri();
				}
				URL url = new URL(uri);
				if(proxy != null){
					conn = (HttpURLConnection)url.openConnection(proxy);
				}else{
					conn = (HttpURLConnection)url.openConnection();
				}
				
				// 实际http请求
				doRequest(conn, supportRequestBodyCompress);
				
				// http响应码
				response.setCode(conn.getResponseCode());
				// http响应头
				for(String field : conn.getHeaderFields().keySet()){
					response.addHeader(field, conn.getHeaderField(field));
				}
				
				// 读取响应
				byte[] r = readResponse(conn);
				// 支持GZIP返回
				String encoding = conn.getHeaderField("Content-Encoding");
				if((encoding != null) && (encoding.equals("gzip"))){
					r = GzipUtil.uncompress(r);
				}
				String responseText = new String(r, charset);
				
				response.setBody(responseText);
			}finally{
				if(conn != null)
					conn.disconnect();
			}
			
			return response;
		}
		
		public void download(File file) throws IOException{
			download(file, false);
		}
		
		/**
		 * 根据URL下载文件
		 * @param file
		 * @throws IOException
		 */
		public void download(File file, boolean supportRequestBodyCompress) throws IOException{
			HttpURLConnection conn = null;
			
			try{
				if(!isPosttRequest()){
					// 构造http get url
					buildGetUri();
				}
				URL url = new URL(uri);
				if(proxy != null){
					conn = (HttpURLConnection)url.openConnection(proxy);
				}else{
					conn = (HttpURLConnection)url.openConnection();
				}
				
				// 实际http请求
				doRequest(conn, supportRequestBodyCompress);
				
				// 保存响应结果到文件
				readResponseAsFile(conn, file);
			}finally{
				if(conn != null)
					conn.disconnect();
			}
		}
		
		private void doRequest(HttpURLConnection conn, boolean supportRequestBodyCompress)throws IOException{
			conn.setDoInput(true);
			conn.setDoOutput(true);
			conn.setUseCaches(false);
			conn.setConnectTimeout(connectTimeout);
			conn.setReadTimeout(readTimeout);
			conn.setRequestMethod(method);
			
			// add header
			if(!headerMap.containsKey("Connection")){
				conn.setRequestProperty("Connection", "close");
			}
			if(!headerMap.containsKey("Accept-Encoding")){
				conn.setRequestProperty("Accept-Encoding", "gzip");
			}
			if(isPosttRequest() && !headerMap.containsKey("Content-Type")){
				conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
			}
			if(headerMap != null){
				for(String headerName : headerMap.keySet()){
					conn.setRequestProperty(headerName, headerMap.get(headerName));
				}
			}
			
			// add body
			byte[] bodyData = null;
			if(isPosttRequest()){
				if(bodyString != null){
					bodyData = bodyString.getBytes(charset);
					if(supportRequestBodyCompress && (bodyData.length >= 256)){
						conn.setRequestProperty("contentCompress", "gzip");
						bodyData = GzipUtil.compress(bodyData);
					}
				}else{
					if(!form.isEmpty()){
						bodyData = buildParamter(form).getBytes(charset);
						if(supportRequestBodyCompress && (bodyData.length >= 256)){
							conn.setRequestProperty("contentCompress", "gzip");
							bodyData = GzipUtil.compress(bodyData);
						}
					}
				}
			}
			
			conn.connect();
			
			// add body
			if(bodyData != null){
				conn.getOutputStream().write(bodyData);
				conn.getOutputStream().flush();
			}
		}
		
		private String buildGetUri(){
			String p = buildParamter(form);
			
			if(p.length() > 0){
				if(uri.indexOf('&') != -1){//已经有参数
					uri += "&" + p;
				}else{
					uri +=	"?" + p;
				}
			}
			
			return uri;
		}
	}
	
	public static class HttpResponse {
		/** 响应码 */
		private int code;
		/** 响应头 */
		private Map<String, String> headerMap = new HashMap<String, String>();
		/** 响应体 */
		private String body;
		
		public void addHeader(String name, String value){
			headerMap.put(name, value);
		}
		
		public int getCode() {
			return code;
		}
		public void setCode(int code) {
			this.code = code;
		}
		public Map<String, String> getHeaderMap() {
			return headerMap;
		}
		public void setHeaderMap(Map<String, String> headerMap) {
			this.headerMap = headerMap;
		}
		public String getBody() {
			return body;
		}
		public void setBody(String body) {
			this.body = body;
		}
		
	}
	
}
